package practica1.logic;

import es.miguelgonzalez.jgraficacomida.GraficaComidaIncorrectValueException;
import java.util.ArrayList;
import practica1.Practica1;
import practica1.domain.Poblacion;
import practica1.domain.PoblacionPropertyEvent;
import practica1.domain.PoblacionPropertyListener;
import practica1.util.HashCodeUtil;

/**
 * Clase que extiende de Poblacion y se encarga de implementar su lógica
 * @author Miguel González - Ceura
 */
public class LogicPoblacion extends Poblacion {
    private boolean sincronizarComida;
    private boolean modified;
    private boolean simulando;
    private ArrayList<LogicPoblacionListener> listeners;
    
    //Se guarda el experimento lógico que contiene a esta población
    //Facilita el manejo de eventos entre padre e hijo
    //NOTA: Tener cuidado al modificar el código para no romper la relación y
    //probocar errores
    private LogicExperimento experimentoPadre;
    
    /**
     * Constructor de una población lógica
     * @param nombrePoblacion Nombre de la población
     * @param experimentoPadre Experimento lógico que lo contiene
     */
    public LogicPoblacion(String nombrePoblacion, LogicExperimento experimentoPadre) {
        super(nombrePoblacion, experimentoPadre.getNumDiasExperimento());
        super.addPropertyChangeListener(new PoblacionPropertyListener() {
            @Override
            public void propertyChange(PoblacionPropertyEvent propertyChange) {
                firePoblacionChange();
            }
        });
        this.experimentoPadre = experimentoPadre;
        sincronizarComida = true;
        modified = false;
        listeners = new ArrayList<LogicPoblacionListener>();
        try {
            //Inicializamos el modelo de la gráfica
            modeloGraficaComida.setNumDias(
                    experimentoPadre.getNumDiasExperimento());
            modeloGraficaComida.setLimiteSuperiorComida(
                    experimentoPadre.getLimiteAlimentacionMaxima());
            modeloGraficaComida.setDiaMax(
                    experimentoPadre.getNumDiasExperimento()/2);
            modeloGraficaComida.setAlimentoMax(
                    experimentoPadre.getLimiteAlimentacionMaxima()/2);
            modeloGraficaComida.setAlimentoInicial(
                    experimentoPadre.getLimiteAlimentacionMaxima()/2);
            modeloGraficaComida.setAlimentoFinal(
                    experimentoPadre.getLimiteAlimentacionMaxima()/2);
        } catch (GraficaComidaIncorrectValueException ex) {
            Practica1.log.error("Error inicializando el modelo de la gráfica");
        }
    }

    /**
     * Devuelve el nombre de la población
     * @return String nombrePoblacion
     */
    @Override
    public String toString() {
        return getNombrePoblacion();
    }
    
    /**
     * Compara una población lógica
     * @param poblacion Object LogicPoblacion
     * @return Devuelve verdad si son iguales, sino falso
     */
    @Override
    public boolean equals(Object poblacion) {
        if(poblacion != null) {
            if(poblacion.getClass().equals(LogicPoblacion.class)) {
                LogicPoblacion p = (LogicPoblacion) poblacion;
                
                if(p.getFecha().equals(getFecha()) &&
                        p.getEscalaTemperatura().equals(getEscalaTemperatura())
                        && p.getLuminosidad().equals(getLuminosidad()) 
                        && p.getNombrePoblacion().equals(getNombrePoblacion())
                        && p.getTamanioPoblacion() == getTamanioPoblacion() && 
                        p.getTemperatura() == getTemperatura() &&
                        sincronizarComida == p.isSincronizarComida() &&
                        p.getExperimentoPadre().getFichExperimento().equals(
                        experimentoPadre.getFichExperimento()) &&
                        p.getExperimentoPadre().getNombreExperimento().equals(
                        experimentoPadre.getNombreExperimento())) {
                    //Comprobamos los modelos después ya que son más pesados
                    //de comparar
                    if(p.getModeloGraficaComida().equals(
                            getModeloGraficaComida()) && p.getModeloPoblacionTabla()
                            .equals(getModeloPoblacionTabla())) {
                        return true;
                    }
                }
            }
        }
        
        return false;
    }
    
    /**
     * Devuelve el hashCode de la población lógica
     * @return int hashCode
     */
    @Override
    public int hashCode() {
        int result = HashCodeUtil.SEED;
        
        result = HashCodeUtil.hash(result, super.hashCode());
        result = HashCodeUtil.hash(result, sincronizarComida);
        result = HashCodeUtil.hash(result, experimentoPadre);
        
        return result;
    }
    
    @Override
    public LogicPoblacion clone() {
        Poblacion pob = super.clone();
        
        LogicPoblacion lPob = new LogicPoblacion(nombrePoblacion, experimentoPadre);
        lPob.setNumDias(pob.getNumDias());
        lPob.setSincronizarComida(sincronizarComida);
        lPob.setTemperatura(pob.getTemperatura());
        lPob.setTamanioPoblacion(pob.getTamanioPoblacion());
        lPob.setEscalaTemperatura(pob.getEscalaTemperatura());
        lPob.setFecha(pob.getFecha());
        lPob.setLuminosidad(pob.getLuminosidad());
        lPob.setModeloComentarios(pob.getModeloComentarios());
        lPob.setModeloGraficaComida(pob.getModeloGraficaComida());
        lPob.setModeloPoblacionTabla(pob.getModeloPoblacionTabla());
        lPob.setSimulacionBacterias(pob.getSimulacionBacterias());
        
        
        
        return lPob;
        
    }
    
    /**
     * Permite suscribirse a los cambios en el modelo lógico de la población
     * @param listener LogicPoblacionListener
     */
    public void addLogicPoblacionListener(LogicPoblacionListener listener) {
        if(listener != null) {
            listeners.add(listener);
        }
    }
    
    /**
     * Permite desuscribirse a los cambios en el modelo lógico de la población
     * @param listener LogicPoblacionListener
     */
    public void removeLogicPoblacionListener(LogicPoblacionListener listener) {
        if(listener != null) {
            listeners.remove(listener);
        }
    }
    
    /**
     * Devuelve el experimento que contiene la población lógica
     * @return LogicExperimento
     */
    public LogicExperimento getExperimentoPadre() {
        return experimentoPadre;
    }
    
    /**
     * Devuelve si la población lógica ha sido modificada
     * @return Devuelve verdad si la población lógica ha sido modificada, sino falso
     */
    public boolean isModified() {
        return modified;
    }
    
    /**
     * Establece el estado de la población lógica
     * @param modified Verdad si está modificada, falso sino
     */
    public void setModified(boolean modified) {
        this.modified = modified;
    }
    
    /**
     * Devuelve si se está sincronizando la comida con la tabla
     * @return Verdad si lo está haciendo, sino falso
     */
    public boolean isSincronizarComida() {
        return sincronizarComida;
    }

    /**
     * Establece si la comida debe sincronziarse con la tabla
     * @param sincronizarComida Verdad si lo debe hacer, sino falso
     */
    public void setSincronizarComida(boolean sincronizarComida) {
        this.sincronizarComida = sincronizarComida;
        firePoblacionChange();
    }

    /**
     * Notifica a todos los que escuchan el modelo lógico de la población
     * que ha sido modificada
     */
    private void firePoblacionChange() {
        //Si no está modificado, lo ponemos modificado y notificamos
        if(!modified) {
            modified = true;
            for(LogicPoblacionListener l : listeners) {
                l.poblacionChange(new LogicPoblacionEvent(this));
            }
        }
    }
    
    public void setSimulando(boolean simulando) {
        this.simulando = simulando;
    }
    
    public boolean isSimulando() {
        return simulando;
    }
}
